hilos-agent 0.1.10 → 0.1.11

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/handler.mjs +37 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hilos-agent",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Run your own coding agent (Claude Code / Codex / Cursor) as an autonomous teammate in a hilos channel. Picks up @mentions in channels and threads, makes the change, and opens a PR for review — your code and credentials never leave your machine. (Approve-before-push is available via gate:true.)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/handler.mjs CHANGED
@@ -171,14 +171,19 @@ const CODE_SIGNAL = "__CODE__";
171
171
 
172
172
  /**
173
173
  * Decide — with the LLM, not a word list — whether the latest message wants a
174
- * code change or a conversational reply, and (for chat) produce that reply in the
175
- * SAME call. The model reads the whole conversation, so it judges by intent and
174
+ * code change or a conversational reply, and produce the payload in the SAME
175
+ * call. The model reads the whole conversation, so it judges by intent and
176
176
  * context, in any language: "just code it", "dale, hazlo", "yeah go for it" after
177
177
  * a request → code; "how does this work?", "thoughts?", "thanks" → chat.
178
178
  *
179
+ * For a code request it ALSO distills an imperative task brief — resolving
180
+ * references like "this" / "keep working on it" against the conversation — so the
181
+ * coding agent gets a clean spec instead of a noisy transcript (which, with the
182
+ * agent's own past "no changes" messages in it, made it reply instead of edit).
183
+ *
179
184
  * Returns one of:
180
185
  * { aborted: true }
181
- * { code: true } → run the coding flow
186
+ * { code: true, task } → run the coding flow with `task` as the spec
182
187
  * { code: false, reply, error } → post `reply` as a chat message
183
188
  *
184
189
  * `error` is set when the model produced nothing (so the caller can be honest
@@ -192,8 +197,11 @@ async function routeIntent({ name, repoFullName, transcript, workspaceMemory, cf
192
197
  `${repoFullName}. Read the conversation and judge what the LATEST message wants from you.\n\n` +
193
198
  `If it is asking you to make a code or repository change — implement/fix/refactor/adjust ` +
194
199
  `something, continue or finish work discussed above, or give a go-ahead to act ("yeah, do ` +
195
- `it", "go for it", "just code it", "approved", "dale", in ANY language) — respond with ` +
196
- `EXACTLY this and nothing else:\n${CODE_SIGNAL}\n\n` +
200
+ `it", "go for it", "just code it", "approved", "dale", in ANY language) — respond with the ` +
201
+ `token ${CODE_SIGNAL} on the FIRST line, then on the next lines a short, imperative spec of ` +
202
+ `exactly what to build or change, resolving any references ("this", "keep working on it") ` +
203
+ `using the conversation. Example:\n${CODE_SIGNAL}\nAdd a hover popover to message reactions ` +
204
+ `that lists who reacted with each emoji.\n\n` +
197
205
  `Otherwise — a question, a greeting, general discussion, or they explicitly don't want code ` +
198
206
  `yet — just reply to them concisely and directly as a single chat message (no preamble, no ` +
199
207
  `headings). When unsure, prefer to act (${CODE_SIGNAL}); this is a build channel.\n\n` +
@@ -208,30 +216,35 @@ async function routeIntent({ name, repoFullName, transcript, workspaceMemory, cf
208
216
  if (run.aborted || signal?.aborted) return { aborted: true };
209
217
  const out = (run.stdout || "").trim();
210
218
  if (!out) return { code: false, reply: null, error: run.error || new Error("no output") };
211
- if (new RegExp(`^${CODE_SIGNAL}\\b`).test(out)) return { code: true };
219
+ if (new RegExp(`^${CODE_SIGNAL}\\b`).test(out)) {
220
+ const nl = out.indexOf("\n");
221
+ return { code: true, task: nl >= 0 ? out.slice(nl + 1).trim() : "" };
222
+ }
212
223
  return { code: false, reply: out };
213
224
  }
214
225
 
215
226
  /**
216
- * The task text handed to the coding CLI. The mention is always the instruction,
217
- * but it's almost never self-contained: a reply like "yeah, do it" only means
218
- * something against what was just said, and even a direct request is clearer with
219
- * the surrounding discussion. So we ALWAYS prepend the recent conversation when we
220
- * have itthe same context the chat path already gets. Falls back to the bare
221
- * mention only when there's no transcript at all.
227
+ * The prompt handed to the CODING CLI. Framed as an engineering directive, NOT a
228
+ * chat: the old "you're in a team chat, do what the latest message asks" wording
229
+ * made the agent reply conversationally to stdout (e.g. to "how u doing? keep
230
+ * working on this") and edit nothing. So this is imperative "you are a coding
231
+ * agent, edit files now" led by the router's distilled `brief` (what to build),
232
+ * with the conversation included only as background. Falls back to the raw
233
+ * mention when there's no brief.
234
+ * @param {{ message?: { body?: string } | null, context?: { transcript?: string } | null, brief?: string, repoFullName?: string }} [o]
222
235
  */
223
- export function codeTaskPrompt({ message, context }) {
224
- const body = String(message?.body || "").trim();
236
+ export function codeTaskPrompt(o) {
237
+ const { message, context, brief, repoFullName } = o || {};
238
+ const task = (brief && brief.trim()) || String(message?.body || "").trim();
225
239
  const transcript = context?.transcript?.trim();
226
- if (!transcript) return body;
227
- return (
228
- `You're acting on a request in a team chat. Recent conversation (oldest first):\n` +
229
- `${transcript}\n\n` +
230
- `The latest message is your instruction: "${body}". Do what it asks, using the ` +
231
- `conversation above for context it often carries the actual spec (e.g. a reply ` +
232
- `like "go for it" refers to what was just discussed). Make the change directly; ` +
233
- `it will be opened as a PR for review.`
234
- );
240
+ const where = repoFullName ? ` in the git repository ${repoFullName}` : "";
241
+ let p =
242
+ `You are a coding agent working${where}. Implement the following by EDITING FILES now — ` +
243
+ `make the changes directly, do not just describe them, do not ask questions:\n\n${task}`;
244
+ if (transcript) {
245
+ p += `\n\nBackground from the team's discussion (context only — the task above is what to do):\n${transcript}`;
246
+ }
247
+ return p;
235
248
  }
236
249
 
237
250
  /** owner/name from a GitHub remote URL (https or ssh), or null. */
@@ -663,7 +676,7 @@ export async function handleTask({ message, channelId, tool, me, caps = {} }, cf
663
676
  return { status: "cancelled", branch };
664
677
  };
665
678
 
666
- const staged = await runAndStage(codeTaskPrompt({ message, context }));
679
+ const staged = await runAndStage(codeTaskPrompt({ message, context, brief: routed.task, repoFullName }));
667
680
  if (staged.aborted) return await postStopped();
668
681
  if (staged.empty) {
669
682
  let body;