@nopeek/agent-bridge 0.7.16 → 0.7.17

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.
@@ -7,7 +7,9 @@ export type ToolProgressEvent = {
7
7
  /** One Telegram-style line. Running events only; completed is silent. */
8
8
  export declare function formatToolLine(ev: ToolProgressEvent): string | null;
9
9
  /** Hint so Hermes ends a tool-using turn with a recap, like Telegram. */
10
- export declare const RECAP_HINT = "When this turn used tools (read/write files, commands, skills, memory), end your reply with a short recap: what was going on, what you did, and the result. Skip the recap for simple chat with no tools. No em dashes.";
10
+ export declare const RECAP_HINT = "When this turn used tools, your LAST message must be a short phone briefing: (1) what was wrong, (2) what you did, (3) the solution / current state. Never end on a tool list, a diff, code, or \"tasks completed\". Skip the briefing only for simple chat with no tools. No em dashes.";
11
+ /** Posted when tools ran but the model never wrote a human recap. */
12
+ export declare const MISSING_BRIEFING = "Done. I finished that work. Ask if you want the recap: what was wrong, what I did, and where things stand.";
11
13
  export interface StreamHandle {
12
14
  append(chunk: string): void;
13
15
  replace(full: string): void;
@@ -35,6 +37,8 @@ export type TurnFinishKind = "streamed" | "sent" | "empty";
35
37
  export declare function unpublishedTail(full: string, published: string): string;
36
38
  /** Unified diffs / `| review` dumps should never land in the chat. */
37
39
  export declare function isDumpLine(line: string): boolean;
40
+ /** True when the text has a user-readable recap, not just tools/dumps. */
41
+ export declare function isHumanBriefing(text: string): boolean;
38
42
  export declare function isToolDump(text: string): boolean;
39
43
  /** Drop dump lines from a live chunk without swallowing normal punctuation. */
40
44
  export declare function stripDumps(text: string): string;
@@ -49,7 +49,9 @@ function tidyLabel(raw) {
49
49
  return s.length > 80 ? `${s.slice(0, 77)}...` : s;
50
50
  }
51
51
  /** Hint so Hermes ends a tool-using turn with a recap, like Telegram. */
52
- export const RECAP_HINT = "When this turn used tools (read/write files, commands, skills, memory), end your reply with a short recap: what was going on, what you did, and the result. Skip the recap for simple chat with no tools. No em dashes.";
52
+ export const RECAP_HINT = "When this turn used tools, your LAST message must be a short phone briefing: (1) what was wrong, (2) what you did, (3) the solution / current state. Never end on a tool list, a diff, code, or \"tasks completed\". Skip the briefing only for simple chat with no tools. No em dashes.";
53
+ /** Posted when tools ran but the model never wrote a human recap. */
54
+ export const MISSING_BRIEFING = "Done. I finished that work. Ask if you want the recap: what was wrong, what I did, and where things stand.";
53
55
  /** Text in `full` that has not already been posted as commentary. */
54
56
  export function unpublishedTail(full, published) {
55
57
  const f = full.trim();
@@ -95,8 +97,35 @@ export function isDumpLine(line) {
95
97
  return true;
96
98
  if (/^index [0-9a-f]+\.\.[0-9a-f]+/.test(l))
97
99
  return true;
100
+ // Unified-diff / review lines: "+ const x =" not "+1 country".
101
+ if (/^[+-]\s{2,}\S/.test(l))
102
+ return true;
103
+ if (/^[+-]\s*(import |from |export |const |let |function |class |def |if cmd\b|return )/.test(l))
104
+ return true;
105
+ if (/^[+-]\s*["'`{]/.test(l))
106
+ return true;
98
107
  return false;
99
108
  }
109
+ function isToolProgressLine(line) {
110
+ const l = line.trim();
111
+ if (!l)
112
+ return false;
113
+ return /^.+ \S+: ".+"$/.test(l) || /^.+ \S+\.\.\.$/.test(l);
114
+ }
115
+ /** True when the text has a user-readable recap, not just tools/dumps. */
116
+ export function isHumanBriefing(text) {
117
+ const prose = text
118
+ .split("\n")
119
+ .filter((l) => {
120
+ const t = l.trim();
121
+ return Boolean(t) && !isDumpLine(t) && !isToolProgressLine(t);
122
+ })
123
+ .join("\n")
124
+ .trim();
125
+ if (!prose || isToolDump(prose))
126
+ return false;
127
+ return true;
128
+ }
100
129
  export function isToolDump(text) {
101
130
  const t = text.trim();
102
131
  if (!t)
@@ -264,11 +293,17 @@ export class TurnPublisher {
264
293
  await this.commitText(this.held);
265
294
  this.held = "";
266
295
  const rest = unpublishedTail(reply.trim(), this.published);
267
- if (!rest)
268
- return this.published.trim() ? "streamed" : "empty";
269
- this.opts.stopTyping();
270
- const sent = await this.sendBubbles(rest);
271
- return sent ? "sent" : this.published.trim() ? "streamed" : "empty";
296
+ if (rest) {
297
+ this.opts.stopTyping();
298
+ await this.sendBubbles(rest);
299
+ }
300
+ if (this.toolsUsed && reply.trim() && !isHumanBriefing(this.published)) {
301
+ this.opts.stopTyping();
302
+ await this.sendBubbles(MISSING_BRIEFING);
303
+ }
304
+ if (!this.published.trim())
305
+ return "empty";
306
+ return rest || this.toolsUsed ? "sent" : "streamed";
272
307
  }
273
308
  async fail(text) {
274
309
  this.cancelStart();
@@ -382,7 +417,11 @@ export class TurnPublisher {
382
417
  if (extra)
383
418
  s.append(extra);
384
419
  const parts = splitBubbles(final, this.maxBubbleChars);
385
- const first = parts[0] || final;
420
+ if (!parts.length) {
421
+ await s.done("…").catch(() => { });
422
+ return;
423
+ }
424
+ const first = parts[0];
386
425
  await s.done(first).catch(() => { });
387
426
  this.published += first;
388
427
  for (const more of parts.slice(1)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nopeek/agent-bridge",
3
- "version": "0.7.16",
3
+ "version": "0.7.17",
4
4
  "description": "Run your own agents as E2EE NoPeek bots. Pairs with one-time codes (multiple accounts per computer), runs every bot each account owns, and pipes messages to any command or webhook.",
5
5
  "type": "module",
6
6
  "license": "MIT",