@nopeek/agent-bridge 0.7.17 → 0.7.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/dist/backends.js CHANGED
@@ -25,8 +25,7 @@ const SOUL_TEMPLATE = `You are {{NAME}} (handle @{{HANDLE}}), a personal AI agen
25
25
  NoPeek end-to-end-encrypted messenger. A human messages you in a chat; you help;
26
26
  you reply. Everything you output is sent verbatim as a chat message.
27
27
 
28
- Voice: friendly, sharp, concise. This is a chat, not a report short paragraphs
29
- or brief lists, no markdown headers. Answer the question directly.
28
+ Voice: friendly, sharp, concise. This is a chat, not a report. Telegram markdown is ok (**bold**, lists, inline code, fenced copy blocks). Answer the question directly.
30
29
 
31
30
  You have your own memory of past conversations in this chat (each chat is a
32
31
  separate ongoing thread). Remember what the humans tell you.
@@ -293,7 +292,7 @@ you reply. Your replies are chat messages — every word you print is sent verba
293
292
 
294
293
  ## Voice
295
294
  - Friendly, sharp, practical. Concise by default — this is a chat, not a report.
296
- - Plain language. No markdown headers in replies; short paragraphs or brief lists only.
295
+ - Plain language. Telegram markdown is ok: **bold** labels, lists, inline code, and fenced copy blocks. No # headers.
297
296
 
298
297
  ## Memory
299
298
  - This profile is yours alone: your soul (this file) and your memories
@@ -7,7 +7,7 @@ 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, 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.";
10
+ export declare const RECAP_HINT = "When this turn used tools, your LAST message must be ONE phone briefing (not split). Use **bold** labels and blank lines like Telegram: **What was wrong**, **What I did**, **Current state**. Put copyable commands in ``` fences. Never end on a tool list, a diff, or \"tasks completed\". Skip the briefing only for simple chat with no tools. No em dashes.";
11
11
  /** Posted when tools ran but the model never wrote a human recap. */
12
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.";
13
13
  export interface StreamHandle {
@@ -78,10 +78,12 @@ export declare class TurnPublisher {
78
78
  /** After `bubbleGapMs` of silence, seal the current bubble. Next text is new. */
79
79
  private armGap;
80
80
  private enqueue;
81
- /** Open or append the current commentary/answer stream with this chunk only. */
81
+ /** After tools start, hold prose until finish() so the briefing is one bubble. */
82
82
  private appendCommentary;
83
83
  /** Finalize the current commentary so later tools land below it. */
84
84
  private commitText;
85
+ /** Final recap stays one Telegram-style bubble. Dumps still dropped. */
86
+ private sendBriefing;
85
87
  private sendBubbles;
86
88
  private addProgressLine;
87
89
  private closeProgress;
@@ -49,7 +49,7 @@ 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, 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.";
52
+ export const RECAP_HINT = "When this turn used tools, your LAST message must be ONE phone briefing (not split). Use **bold** labels and blank lines like Telegram: **What was wrong**, **What I did**, **Current state**. Put copyable commands in ``` fences. Never end on a tool list, a diff, or \"tasks completed\". Skip the briefing only for simple chat with no tools. No em dashes.";
53
53
  /** Posted when tools ran but the model never wrote a human recap. */
54
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.";
55
55
  /** Text in `full` that has not already been posted as commentary. */
@@ -100,6 +100,8 @@ export function isDumpLine(line) {
100
100
  // Unified-diff / review lines: "+ const x =" not "+1 country".
101
101
  if (/^[+-]\s{2,}\S/.test(l))
102
102
  return true;
103
+ if (/^[+-][A-Za-z_]/.test(l))
104
+ return true;
103
105
  if (/^[+-]\s*(import |from |export |const |let |function |class |def |if cmd\b|return )/.test(l))
104
106
  return true;
105
107
  if (/^[+-]\s*["'`{]/.test(l))
@@ -148,8 +150,6 @@ export function stripDumps(text) {
148
150
  return text;
149
151
  if (!text.includes("\n"))
150
152
  return isDumpLine(text) ? "" : text;
151
- if (isToolDump(text))
152
- return "";
153
153
  return text
154
154
  .split("\n")
155
155
  .filter((l) => !isDumpLine(l))
@@ -275,13 +275,16 @@ export class TurnPublisher {
275
275
  if (this.seen.has(line) && this.progressLines.includes(line))
276
276
  return;
277
277
  this.seen.add(line);
278
+ const firstTool = !this.toolsUsed;
278
279
  this.toolsUsed = true;
279
280
  this.cancelStart();
280
281
  this.cancelGap();
281
282
  this.enqueue(async () => {
282
- const pending = this.held;
283
- this.held = "";
284
- await this.commitText(pending);
283
+ if (firstTool) {
284
+ const pending = this.held;
285
+ this.held = "";
286
+ await this.commitText(pending);
287
+ }
285
288
  await this.addProgressLine(line);
286
289
  });
287
290
  }
@@ -290,16 +293,20 @@ export class TurnPublisher {
290
293
  this.cancelGap();
291
294
  await this.write;
292
295
  await this.closeProgress();
293
- await this.commitText(this.held);
296
+ const pending = this.held;
294
297
  this.held = "";
298
+ if (!this.toolsUsed) {
299
+ await this.commitText(pending);
300
+ }
295
301
  const rest = unpublishedTail(reply.trim(), this.published);
296
- if (rest) {
302
+ const briefing = rest || (this.toolsUsed ? pending.trim() : "");
303
+ if (briefing) {
297
304
  this.opts.stopTyping();
298
- await this.sendBubbles(rest);
305
+ await this.sendBriefing(briefing);
299
306
  }
300
307
  if (this.toolsUsed && reply.trim() && !isHumanBriefing(this.published)) {
301
308
  this.opts.stopTyping();
302
- await this.sendBubbles(MISSING_BRIEFING);
309
+ await this.sendBriefing(MISSING_BRIEFING);
303
310
  }
304
311
  if (!this.published.trim())
305
312
  return "empty";
@@ -354,26 +361,13 @@ export class TurnPublisher {
354
361
  console.error(`[turn] ${err.message}`);
355
362
  });
356
363
  }
357
- /** Open or append the current commentary/answer stream with this chunk only. */
364
+ /** After tools start, hold prose until finish() so the briefing is one bubble. */
358
365
  async appendCommentary(chunk) {
359
366
  if (!chunk)
360
367
  return;
361
368
  if (this.toolsUsed) {
362
369
  await this.closeProgress();
363
370
  this.held += chunk;
364
- const cut = this.held.lastIndexOf("\n\n");
365
- if (cut >= 0) {
366
- const ready = this.held.slice(0, cut);
367
- this.held = this.held.slice(cut + 2);
368
- this.opts.stopTyping();
369
- await this.sendBubbles(ready);
370
- }
371
- else if (this.held.length >= this.maxBubbleChars) {
372
- const ready = this.held;
373
- this.held = "";
374
- this.opts.stopTyping();
375
- await this.sendBubbles(ready);
376
- }
377
371
  return;
378
372
  }
379
373
  this.opts.stopTyping();
@@ -442,6 +436,15 @@ export class TurnPublisher {
442
436
  this.opts.stopTyping();
443
437
  await this.sendBubbles(pending);
444
438
  }
439
+ /** Final recap stays one Telegram-style bubble. Dumps still dropped. */
440
+ async sendBriefing(text) {
441
+ const cleaned = stripDumps(text.replace(/\r\n/g, "\n")).trim();
442
+ if (!cleaned || isToolDump(cleaned))
443
+ return false;
444
+ await this.sink.send(cleaned);
445
+ this.published += this.published ? `\n\n${cleaned}` : cleaned;
446
+ return true;
447
+ }
445
448
  async sendBubbles(text) {
446
449
  const parts = splitBubbles(text, this.maxBubbleChars);
447
450
  if (!parts.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nopeek/agent-bridge",
3
- "version": "0.7.17",
3
+ "version": "0.7.18",
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",