@nopeek/agent-bridge 0.7.16 → 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 +2 -3
- package/dist/tool-progress.d.ts +8 -2
- package/dist/tool-progress.js +69 -27
- package/package.json +1 -1
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
|
|
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.
|
|
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
|
package/dist/tool-progress.d.ts
CHANGED
|
@@ -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
|
|
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
|
+
/** 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;
|
|
@@ -74,10 +78,12 @@ export declare class TurnPublisher {
|
|
|
74
78
|
/** After `bubbleGapMs` of silence, seal the current bubble. Next text is new. */
|
|
75
79
|
private armGap;
|
|
76
80
|
private enqueue;
|
|
77
|
-
/**
|
|
81
|
+
/** After tools start, hold prose until finish() so the briefing is one bubble. */
|
|
78
82
|
private appendCommentary;
|
|
79
83
|
/** Finalize the current commentary so later tools land below it. */
|
|
80
84
|
private commitText;
|
|
85
|
+
/** Final recap stays one Telegram-style bubble. Dumps still dropped. */
|
|
86
|
+
private sendBriefing;
|
|
81
87
|
private sendBubbles;
|
|
82
88
|
private addProgressLine;
|
|
83
89
|
private closeProgress;
|
package/dist/tool-progress.js
CHANGED
|
@@ -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
|
|
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
|
+
/** 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,37 @@ 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 (/^[+-][A-Za-z_]/.test(l))
|
|
104
|
+
return true;
|
|
105
|
+
if (/^[+-]\s*(import |from |export |const |let |function |class |def |if cmd\b|return )/.test(l))
|
|
106
|
+
return true;
|
|
107
|
+
if (/^[+-]\s*["'`{]/.test(l))
|
|
108
|
+
return true;
|
|
98
109
|
return false;
|
|
99
110
|
}
|
|
111
|
+
function isToolProgressLine(line) {
|
|
112
|
+
const l = line.trim();
|
|
113
|
+
if (!l)
|
|
114
|
+
return false;
|
|
115
|
+
return /^.+ \S+: ".+"$/.test(l) || /^.+ \S+\.\.\.$/.test(l);
|
|
116
|
+
}
|
|
117
|
+
/** True when the text has a user-readable recap, not just tools/dumps. */
|
|
118
|
+
export function isHumanBriefing(text) {
|
|
119
|
+
const prose = text
|
|
120
|
+
.split("\n")
|
|
121
|
+
.filter((l) => {
|
|
122
|
+
const t = l.trim();
|
|
123
|
+
return Boolean(t) && !isDumpLine(t) && !isToolProgressLine(t);
|
|
124
|
+
})
|
|
125
|
+
.join("\n")
|
|
126
|
+
.trim();
|
|
127
|
+
if (!prose || isToolDump(prose))
|
|
128
|
+
return false;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
100
131
|
export function isToolDump(text) {
|
|
101
132
|
const t = text.trim();
|
|
102
133
|
if (!t)
|
|
@@ -119,8 +150,6 @@ export function stripDumps(text) {
|
|
|
119
150
|
return text;
|
|
120
151
|
if (!text.includes("\n"))
|
|
121
152
|
return isDumpLine(text) ? "" : text;
|
|
122
|
-
if (isToolDump(text))
|
|
123
|
-
return "";
|
|
124
153
|
return text
|
|
125
154
|
.split("\n")
|
|
126
155
|
.filter((l) => !isDumpLine(l))
|
|
@@ -246,13 +275,16 @@ export class TurnPublisher {
|
|
|
246
275
|
if (this.seen.has(line) && this.progressLines.includes(line))
|
|
247
276
|
return;
|
|
248
277
|
this.seen.add(line);
|
|
278
|
+
const firstTool = !this.toolsUsed;
|
|
249
279
|
this.toolsUsed = true;
|
|
250
280
|
this.cancelStart();
|
|
251
281
|
this.cancelGap();
|
|
252
282
|
this.enqueue(async () => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
283
|
+
if (firstTool) {
|
|
284
|
+
const pending = this.held;
|
|
285
|
+
this.held = "";
|
|
286
|
+
await this.commitText(pending);
|
|
287
|
+
}
|
|
256
288
|
await this.addProgressLine(line);
|
|
257
289
|
});
|
|
258
290
|
}
|
|
@@ -261,14 +293,24 @@ export class TurnPublisher {
|
|
|
261
293
|
this.cancelGap();
|
|
262
294
|
await this.write;
|
|
263
295
|
await this.closeProgress();
|
|
264
|
-
|
|
296
|
+
const pending = this.held;
|
|
265
297
|
this.held = "";
|
|
298
|
+
if (!this.toolsUsed) {
|
|
299
|
+
await this.commitText(pending);
|
|
300
|
+
}
|
|
266
301
|
const rest = unpublishedTail(reply.trim(), this.published);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
302
|
+
const briefing = rest || (this.toolsUsed ? pending.trim() : "");
|
|
303
|
+
if (briefing) {
|
|
304
|
+
this.opts.stopTyping();
|
|
305
|
+
await this.sendBriefing(briefing);
|
|
306
|
+
}
|
|
307
|
+
if (this.toolsUsed && reply.trim() && !isHumanBriefing(this.published)) {
|
|
308
|
+
this.opts.stopTyping();
|
|
309
|
+
await this.sendBriefing(MISSING_BRIEFING);
|
|
310
|
+
}
|
|
311
|
+
if (!this.published.trim())
|
|
312
|
+
return "empty";
|
|
313
|
+
return rest || this.toolsUsed ? "sent" : "streamed";
|
|
272
314
|
}
|
|
273
315
|
async fail(text) {
|
|
274
316
|
this.cancelStart();
|
|
@@ -319,26 +361,13 @@ export class TurnPublisher {
|
|
|
319
361
|
console.error(`[turn] ${err.message}`);
|
|
320
362
|
});
|
|
321
363
|
}
|
|
322
|
-
/**
|
|
364
|
+
/** After tools start, hold prose until finish() so the briefing is one bubble. */
|
|
323
365
|
async appendCommentary(chunk) {
|
|
324
366
|
if (!chunk)
|
|
325
367
|
return;
|
|
326
368
|
if (this.toolsUsed) {
|
|
327
369
|
await this.closeProgress();
|
|
328
370
|
this.held += chunk;
|
|
329
|
-
const cut = this.held.lastIndexOf("\n\n");
|
|
330
|
-
if (cut >= 0) {
|
|
331
|
-
const ready = this.held.slice(0, cut);
|
|
332
|
-
this.held = this.held.slice(cut + 2);
|
|
333
|
-
this.opts.stopTyping();
|
|
334
|
-
await this.sendBubbles(ready);
|
|
335
|
-
}
|
|
336
|
-
else if (this.held.length >= this.maxBubbleChars) {
|
|
337
|
-
const ready = this.held;
|
|
338
|
-
this.held = "";
|
|
339
|
-
this.opts.stopTyping();
|
|
340
|
-
await this.sendBubbles(ready);
|
|
341
|
-
}
|
|
342
371
|
return;
|
|
343
372
|
}
|
|
344
373
|
this.opts.stopTyping();
|
|
@@ -382,7 +411,11 @@ export class TurnPublisher {
|
|
|
382
411
|
if (extra)
|
|
383
412
|
s.append(extra);
|
|
384
413
|
const parts = splitBubbles(final, this.maxBubbleChars);
|
|
385
|
-
|
|
414
|
+
if (!parts.length) {
|
|
415
|
+
await s.done("…").catch(() => { });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const first = parts[0];
|
|
386
419
|
await s.done(first).catch(() => { });
|
|
387
420
|
this.published += first;
|
|
388
421
|
for (const more of parts.slice(1)) {
|
|
@@ -403,6 +436,15 @@ export class TurnPublisher {
|
|
|
403
436
|
this.opts.stopTyping();
|
|
404
437
|
await this.sendBubbles(pending);
|
|
405
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
|
+
}
|
|
406
448
|
async sendBubbles(text) {
|
|
407
449
|
const parts = splitBubbles(text, this.maxBubbleChars);
|
|
408
450
|
if (!parts.length)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nopeek/agent-bridge",
|
|
3
|
-
"version": "0.7.
|
|
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",
|